[Solved] Meaningful way to pass value to component. [Mantra]


#1

I continue (struggling) building my understanding of Mantra building blocks (komposer and DI) and Mantra philosophy.:scream:
Tried to build simple todo (not completely in Mantra style because want to switch to Mantra more consciously)

Dump component:

import React from 'react'

const App = ({resolutions, onSubmitResolution, onChangeInput, value}) => {

    const listOfResolutions = resolutions.map((resolution) => {
        return (
            <h1>{resolution.text}</h1>
        )
    })

    return (
        <div>
            <h1>My Resolutions</h1>
            <form
                className="new-resolution"
                onSubmit={onSubmitResolution}
            >
                <input
                    type="text"
                    onChange={onChangeInput}
                    value={value}
                    name="input"
                />
            </form>
            {listOfResolutions}
        </div>
    )
}

export default App

Container:

import {composeAll, composeWithTracker, compose} from 'react-komposer'
import App from '../components/app'

import { Resolutions } from '../../lib/collections'
import {useDeps} from 'react-simple-di'

let context = {
    value: ""
}

const onSubmitResolution = (event) => {
    event.preventDefault()

    console.log(event)
    console.log("onSubmit = " + context.value)

    Resolutions.insert({
        text: context.value,
        complete: false,
        createdAt: new Date()
    })

    context.value = ""
}

const onChangeInput = (event) => {
    context.value = event.target.value
    console.log(context.value + " ON Change Input")
}

const composer = (props, onData) => {
    const resolutions = Resolutions.find().fetch()
    onData( null, { resolutions})
}

// TODO do smth with this 1
const anotherComposer = (props, onData) => {

    const handle = setInterval(() => {
        const value = context.value;
        onData(null, {value});
    }, 100);

    const cleanup = () => clearInterval(handle)
    return cleanup
}


const depsToPropsMapper = (context, actions) => ({
    onSubmitResolution: onSubmitResolution,
    onChangeInput: onChangeInput
});

const ComposedApp = composeAll(
    composeWithTracker( composer ),
// TODO do smth with this 2
    compose(anotherComposer),
    useDeps(depsToPropsMapper)
)(App)

export default ComposedApp

Could you help to find more elegant way to clear input field onSubmit event?
(Sorry for terrible code):innocent:


#2

You can create an extra file for the actions and inject them with DI. Here an example with a contact list:

Directory structure:

client/modules/contacts/actions/index.js

import contacts from './contacts';

export default {
    contacts
};

client/modules/contacts/actions/contacts.js

export default {

    confirmContactRequest({Meteor}, id) {

        Meteor.call('contacts.confirm', id, function (error) {

            if (error) Materialize.toast(error.reason, 5000);
            else Materialize.toast('Contact confirmed', 3000);
        });
    }

};

client/modules/contacts/components/contacts.jsx

import React from 'react';

class Contacts extends React.Component {

    _renderContacts() {
        const {contacts,userId} = this.props;
        let contactList = null;
        if (contacts) contactList = (
            <tbody>
            {contacts.map(record => {

                // Actions
                let firstCell = (<td><span className="green-text">Confirmed</span></td>);
                if (record.confirmed === false) firstCell = (<td><span className="red-text">Not confirmed</span></td>);
                if (record.contact === userId && record.confirmed === false) firstCell = (
                    <td><a href="#" className="btn" data-id={record._id} onClick={this.confirmRequest.bind(this)}>Confirm
                        Request</a></td>);

                // Display
                let nameCell = (<td>{record.contact}</td>);

                return (
                    <tr key={record._id}>
                        <td>{record.name()}</td>
                        <td>{record.username()}</td>
                        {firstCell}
                    </tr>
                );
            })}
            </tbody>
        );
        return (
            <table>
                <thead>
                <tr>
                    <th>Name</th>
                    <th>Username</th>
                    <th>Status</th>
                </tr>
                </thead>
                {contactList}
            </table>
        );
    }

    render() {

        const {loading} = this.props;

        return (
                <div className="col s12 m12 l12">
                    {this._renderContacts()}
                </div>
        );
    }

    confirmRequest(event) {

        event.preventDefault();
        const {confirmContactRequest} = this.props;
        confirmContactRequest($(event.currentTarget).data('id'));
    }
}

export default Contacts;

client/modules/contacts/containers/contacts.js

import Contacts from "../components/contacts.jsx";
import {useDeps} from "react-simple-di";
import {composeWithTracker, composeAll} from "react-komposer";

export const composer = ({context}, onData) => {
    const {Meteor,Collections} = context();

    if (!!Meteor.user()) {

        const userId = Meteor.userId();

        if (Meteor.subscribe('users.contacts').ready() && Meteor.subscribe('contacts.list', true).ready()) {

            const contacts = Collections.Contacts.find({}, {sort: {name: 1}}).fetch();

            onData(null, {contacts, userId});
        } else {

            onData(null, {userId});
        }
    }
    else {

        onData(null);
    }
};

export const depsMapper = (context, actions) => ({
    confirmContactRequest: actions.contacts.confirmContactRequest,
    context: () => context
});

export default composeAll(
    composeWithTracker(composer),
    useDeps(depsMapper)
)(Contacts);

The actionsfile is simple: You only have the code that should be executed in a function that you export. In the container you bind the method in the dependency mapper, so that you can call them in the component then. Please note that it doesn;t work if the function name in the container and the name of the action method is the same. That is the reason you see confirmRequest calling confirmContactRequest.


#3

Thank you :grin:

Now it looks like this (without removing autopublish and insecure)

#Component:
import React, {Component} from 'react’
import ReactDOM from ‘react-dom’

export default class App extends Component {
    onSubmit(e) {
        e.preventDefault()

        const {onSubmitResolution} = this.props
        const node = ReactDOM.findDOMNode(this.refs.resolution)
        onSubmitResolution(node.value.trim())
        node.value = ""

    }

    render() {
        const {resolutions} = this.props

        const displayResolutions = resolutions.map((resolution) => {
            return (
                <h1>{resolution.text}</h1>
            )
        })

        return (
            <div>
                <h1>My Resolutions</h1>
                <form
                    className="new-resolution"
                    onSubmit={this.onSubmit.bind(this)}
                >
                    <input
                        type="text"
                        name="input"
                        ref="resolution"
                    />
                </form>
                {displayResolutions}
            </div>
        )
    }
}

Container:

import {composeAll, composeWithTracker, compose} from 'react-komposer'
import App from '../components/app'

import {useDeps} from 'react-simple-di'

const composer = ({context}, onData) => {
    const {Collections} = context()
    const resolutions = Collections.Resolutions.find().fetch()
    onData( null, { resolutions})
}

const depsToPropsMapper = (context, actions) => ({
    onSubmitResolution: actions.resolutions.submitResolution,
    context: () => context
})

const ComposedApp = composeAll(
    composeWithTracker( composer ),
    useDeps(depsToPropsMapper)
)(App)

export default ComposedApp

Very simple and elegant, thank you again :slight_smile: