ReactJS: access child method - javascript

I'm trying to make a simple form system for a project I'm working on, and the idea is simple. I have a FormStore where forms can be registered, components can be registered to those forms and all the data can be retrieved.
I want to make this as automated as possible, so when the <Form> element is mounted, it should search any children for a isFormItem property, and automatically register that input field with some callback to get the values, defined within the input element.
The issue I'm facing is that I don't know how I would access the child component from the higher level component. I don't think there's a way to use refs, since those have to be manually assigned, and that's what I'm trying to avoid.
This is the relevant snippet from my Form.react.jsx. This is where I try to attach each Input field to the form.
componentDidMount() {
const name = this.props.name ||
'form_' + Math.floor(Math.random() * 100000000);
FormStore.registerForm(name);
React.Children.forEach(this.props.children, (child) => {
if (selectn('props.isFormItem', child)) {
// I need to call child.attachToForm somehow...
}
});
};
And these are the relevant parts of Input.react.jsx.
constructor(props) {
this.attachToForm = this.attachToForm.bind(this);
}
attachToForm(formName) {
const name = this.props.name ||
'formItem_' + Math.floor(Math.random() * 100000000);
FormStore.attachToForm(formName, name, () => this.state.value);
}
I have tried accessing the child component method like this:
React.Children.forEach(this.props.children, (child) => {
if (selectn('props.isFormItem', child)) {
child.type.prototype.attachToForm =
child.type.prototype.attachToForm.bind(child);
child.type.prototype.attachToForm(name);
}
});
But calling the prototype doesn't bind the actual object instance anywhere, so that ended up in having a whole bunch of binds everywhere, and in the end it didn't evne work in addition to being terribly messy all around.
Is there some way of doing this? I am just not seeing it... Any help would be appreciated.

You can pass the formName as a prop to each Input component and then attach each item in it‘s own componentDidMount method instead.
Also, the getDefaultProps method seems handy in your case.
Something like:
getDefaultProps() {
return {
name: 'form_' + Math.floor(Math.random() * 100000000)
}
}
render() {
return <Input formName={this.props.name} />
}
And then in the Input component:
getDefaultProps() {
return {
name: 'formItem_' + Math.floor(Math.random() * 100000000)
}
}
componentDidMount() {
FormStore.attachToForm(this.props.formName, this.props.name, () => this.state.value)
}

Related

Why is this onClick event handler firing twice in my create-react-app

can someone tell me why is this "upvote" onClick handler firing twice?
the logs would indicate it's only running once but the score it controls increases by 2
export default class Container extends Component {
constructor(props) {
super(props);
this.state = {
jokes: [],
};
this.getNewJokes = this.getNewJokes.bind(this);
this.retrieveJokes = this.retrieveJokes.bind(this);
this.upVote = this.upVote.bind(this);
}
upVote(id) {
this.setState(state => {
//find the joke with the matching id and increase score by one
const modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
console.log(modifiedJokes);
return { jokes: modifiedJokes };
});
}
render() {
return (
<div>
<h1>Container</h1>
{this.state.jokes.map(joke => (
<Joke
key={joke.id}
id={joke.id}
joke={joke.joke}
score={joke.score}
upVote={this.upVote}
downVote={this.downVote}
/>
))}
</div>
);
}
}
on the other hand if I rewrite the handler this way, then it fires only once
upVote(id) {
const modifiedJokes = this.state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
this.setState({ jokes: modifiedJokes });
};
My best guess is that in the first case, you are also modifying the state directly, when you do joke.score = joke.score + 1;
Because you are doing this mapping directly on state array variable, and in Javascript, when using array, you are only working with pointer to that array, not creating a copy of that array.
So the mapping function probably takes a shallow copy of the array, and there's where problem happens.
You can use lodash to create a deep copy of the state array before you going to work with it, which will not cause your problem:
https://codesandbox.io/s/great-babbage-lorlm
This doesn't work because React uses synthetic events which are reused when it’s handling is done. Calling a function form of setState will defer evaluation (this can be dangerous when the setState call is event dependent and the event has lost its value).
So this should also work:
this.setState((state,props) => ({ jokes: modifiedJokes}));
I can't locate my source at the moment but, I remember coming across this a while back. I'm sure looking through the React Docs can provide a more in depth explanation
I found out what was wrong. I'll post an answer just in case anyone happens to stumble into this upon encountering the same problem.
When in debug mode react will run certain functions twice to discourage certain operations that might result in bugs. One of these operations is directly modifying state.
when doing
this.setState(state => {
//find the joke with the matching id and increase score by one
const modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
console.log(modifiedJokes);
return { jokes: modifiedJokes };
the map function returns a new array where every elements points to the same element in the original array. therefore by doing
state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
I was effectively directly modifying the state. React runs the setstate function twice in debug mode to weed out this kind of operation.
so the correct "react way"of modifying the attribute of an object nested in an array is to do this instead
this.setState(state =>{
let modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
return {...joke, score:joke.score+1}
}
else{ return joke}
})
return {jokes:modifiedJokes}
})
this way when encountering an element with the correct ID a copy is made of that specific element and the score of that copy is increased by one which doesn't impact the actual element which is still in the state left untouched until it is modified by react committing the new state

How to add class to Vue component via $refs

I need to add class name to some Vue components using their ref names. The ref names are defined in a config file. I would like to do it dynamically, to avoid adding class manually on each Vue component.
I have tried to find each component using $refs and if found, add the class name to the element's class list. The class is added, but it is removed as soon as user interaction begins (e.g. the component is clicked, receives new value etc.)
Here is some sample code I've tried:
beforeCreate() {
let requiredFields = config.requiredFields
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
You can use this:
this.$refs[field].$el.classList.value = this.$refs[field].$el.classList.value + 'my-class'
the only thing that you need to make sure of is that your config.requiredFields must include the ref name as a string and nothing more or less ... you can achieve that with :
//for each ref you have
for (let ref in this.$refs) {
config.requiredFields.push(ref)
}
// so config.requiredFields will look like this : ['one','two]
here is an example of a working sample :
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('one', {
template: '<p>component number one</p>'
})
Vue.component('two', {
template: '<p>component number two</p>'
})
new Vue({
el: "#app",
beforeCreate() {
let requiredFields = ['one','two'] // config.requiredFields should be like this
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
})
.my-class {
color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<one ref="one" ></one>
<two ref="two" ></two>
</div>
I know this question was posted ages ago, but I was playing around with something similar and came across a much easier way to add a class to $refs.
When we reference this.$refs['some-ref'].$el.classList it becomes a DOMTokenList which has a bunch of methods and properties you can access.
In this instance, to add a class it is as simple as
this.$refs['some-ref'].$el.classList.add('some-class')
You've to make sure classList.value is an array. By default its a string.
methods: {
onClick(ref) {
const activeClass = 'active-submenu'
if (!this.$refs[ref].classList.length) {
this.$refs[ref].classList.value = [activeClass]
} else {
this.$refs[ref].classList.value = ''
}
},
},
this post helped me tremendously. I needed to target an element within a v-for loop and I ended up writing a little method for it (i'm using Quasar/Vue).
hopefully this will save someone else some time.
addStyleToRef: function(referEl, indexp, classToAdd) {
//will add a class to a $ref element (even within a v-for loop)
//supply $ref name (referEl - txt), index within list (indexp - int) & css class name (classToAdd txt)
if ( this.$refs[referEl][indexp].$el.classList.value.includes(classToAdd) ){
console.log('class already added')
} else {
this.$refs[referEl][indexp].$el.classList.value = this.$refs[referEl][indexp].$el.classList.value + ' ' + classToAdd
}
}
let tag = this.$refs[ref-key][0];
$(tag).addClass('d-none');
Simply get the tag with ref let tag = this.$refs[ref-key][0]; then put this tag into jquery object $(tag).addClass('d-none'); class will be added to required tag.

React checkbox doesn't work as expected

I had and idea to ad some object style to React project. That is why I have written that class
export class Tagi {
constructor() {
this.members = [{
tag: "No matter",
color: "Aquamarine",
isMarked: true
}];
this.handleTagMarking = this.handleTagMarking.bind(this);
}
add(x) {
this.members.push(x);
}
static fromTable(table) {
let tags = new Tagi();
let shortened = unique(table);
for (let value of shortened) {
let record = {
tag: value,
color: colors[shortened.indexOf(value)],
isMarked: false
}
tags.add(record)
}
return tags;
}
get getAllTags() {
return this.members;
}
handleTagMarking(event){
let x = event.target.value;
const index = this.members.findIndex((element)=>element.tag==x);
const currentMarkStatus = this.members[index].isMarked;
if (currentMarkStatus) this.UnMarkTag(index); else this.MarkTag(index)
console.log(this.members)
}
The last part thereof is event handler, more about it later.
That class is implemented in Parent component like this
let Taggs =Tagi.fromTable(d);
console.log (Taggs.getMarkedTags);
Please note that this is implemented in its render function. It has nothing to do with its state.
Later in this Parent component I send it as props
render() {
const label = this.props.labels;
const SingleCheckbox =()=>{return(label.map((element)=>
<label key={element.tag} ><input type="checkbox" value={element.tag} checked={element.isMarked} onChange={this.props.fn} />{element.tag}</label>))}
return (
<div className="checkbox">
<SingleCheckbox />
</div>);
The problem is that checkbox doesn't react to checking items properly. What I mean by this is that data is changed ( I send to console the data within evenhandler so that is clear) but it does not check the fields what is expected behaviour. I suspect it happens because in Parent, Taggs are not state-linked (but only suspect) Is that correct or could there be other reason? If so, how could I easily reshape the code keeping its object oriented style?
I have just chcecked my initial idea of being-not-a-state.
That is not correct I guess. Now in constructor Taggs are initiated like this:
Taggs:Tagi.fromTable(props.disciplines.map((d) => d.tags)),
and later in render{return()} are called like this
<CheckBox labels ={this.state.Taggs.getAllTags} fn={this.state.Taggs.handleTagMarking}/>
Does not change anything at all

Whats the right way to manipulate a model instance in React?

So I have a React component that accepts an instance of a function constructor (a Car).
The component's job is to display information about the Car and manipulate it based on the Car's public interface (methods and properties).
In the example below, a child component should add an accident on button click.
Question: What is the right way for the child to manipulate properties of the Car instance? The root parent's state stores reference to the instance of the Car, and the children are able to manipulate the Car's properties (like .accidents), but see the various onChange examples for why I'm struggling to find the right React way to do this.
I'd like to avoid a heavy handed solution like Flux to store this state.
Any suggestions would be appreciated!
function Car(name, color) {
this.name = name;
this.color = color;
this.accidents = [];
}
const myCar = new Car('Ferrari', 'Red');
myCar.accidents.push('accident #1');
class Accident extends React.Component {
handleButton1 = () => {
const newAccident = 'accident type1 # ' + Math.floor(Math.random()*100);
this.props.onChange1(newAccident);
}
handleButton2 = () => {
const newAccident = 'accident type2 # ' + Math.floor(Math.random()*100);
this.props.onChange2(newAccident);
}
handleButton3 = () => {
const newAccident = 'accident type3 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
this.props.onChange3();
}
handleButton4 = () => {
const newAccident = 'accident type4 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
// This circumvents React's state management, so the parent doesnt
// rerender when its state changes.
}
render() {
return (
<div>
<button onClick={this.handleButton1}>
Add accident (onChange1)
</button>
<button onClick={this.handleButton2}>
Add accident (onChange2)
</button>
<button onClick={this.handleButton3}>
Add accident (onChange3)
</button>
<button onClick={this.handleButton4}>
Add accident (option 4)
</button>
<ul>
{this.props.accidents.map((a, i) => <li key={i}>{a}</li>)}
</ul>
</div>
)
}
}
class DisplayCard extends React.Component {
state = {
editingCar: this.props.car
}
// Push the new accident into state and set it with the same reference.
onChange1 = (newAccident) => {
this.state.editingCar.accidents.push(newAccident);
// Is this semantically different than calling this.forceUpdate?
this.setState({
editingCar: this.state.editingCar,
});
}
// Clone the existing state we want to update and explicitly set that new state
onChange2 = (newAccident) => {
const newAccidentList = _.cloneDeep(this.state.editingCar.accidents);
newAccidentList.push(newAccident);
// Setting our new accident list like this converts editingCar to a POJO
// editingCar.name is lost because a deep merge does not happen.
this.setState({
editingCar: {
accidents: newAccidentList
},
});
}
// Just force update - this.state.editingCar was manipulated by <Accident />.
onChange3 = () => {
this.forceUpdate();
}
render() {
return (
<div>
<div>Car Name: {this.state.editingCar.name}</div>
<Accident
accidents={this.state.editingCar.accidents}
onChange1={this.onChange1}
onChange2={this.onChange2}
onChange3={this.onChange3}
/>
</div>
);
}
}
ReactDOM.render(
<DisplayCard car={ myCar } />,
document.getElementById('container')
);
Also on JSFiddle if you want to play around: https://jsfiddle.net/jamis0n003/fbkn5xdy/4/
EDIT: The React JS docs suggest integrating with "other libraries", such as Backbone models, using forceUpdate:
https://reactjs.org/docs/integrating-with-other-libraries.html#using-backbone-models-in-react-components
When state is stored in a parent component and a child component wants to manipulate that state, the parent should pass a callback function to the child's props. Then the child calls the callback to notify the parent to modify its own state. The child should never modify props since the change can have unintended consequences due to the way objects are referenced in JavaScript.
If you want to get really fancy, you can use Redux which stores "global" state in the top-most parent component. All child components issue (or dispatch) actions which notify the top-level parent to update its state which is then passed down again to all children components through their props.
What is the right way for the child to manipulate properties of the Car instance?
In general, rely on setState() to update state, which will reliably redraw the view, or if you mutate the data use forceRedraw() to ensure the view is redrawn with the latest data -- but using setState() is much preferred. In either case a child must notify a parent of a change using a callback like you have, but instead of having the child Accident actually change the data, make it a "dumb" component which notifies the parent of an intended change and the parent actually makes the change.
I'd like to avoid a heavy handed solution like Flux to store this state.
You may want to look into MobX, which is popular alternative to Flux/Redux that is a bit easier to get into because it allows you to mutate objects very much in the way you are already doing.

React - Updating state for each child component

Not sure if i'm understanding this correctly. If i'm making a child component, type button, that increments its own counter, is it possible to do so without having 2 separate functions? the following i've done seems like a hack? what if theres X amount of buttons, how would i refactor this code to be more dynamic?
REF's seem to work in the way i can reference the html in the child, but what about the other way? Am i even thinking about this the right way, because component has its own state, should it have its own update method?
/*** #jsx React.DOM */
var MyComponent = React.createClass({
getInitialState: function() {
return {
counter1: 1,
counter2: 1
};
},
increment: function(i) {
if (i === 1) {
this.setState({
counter1: this.state.counter1 + 1
});
} else {
this.setState({
counter2: this.state.counter2 + 1
});
}
},
render: function() {
return ( < div >
<ChildComponent item = {this.state.counter1} click={this.increment.bind(this, 1)}/>
<ChildComponent item={this.state.counter2} click={this.increment.bind(this, 2)}/ >
< /div>
);
}
});
var ChildComponent = React.createClass({
render: function() {
return (
<div>
<h1> Counter {this.props.item} </h1 >
<button onClick = {this.props.click} > ++ < /button>
</div >
);
}
});
React.render( < MyComponent / > , document.body);
I see in your comment that you've settled on putting state in the child component. While that works fine, the power of React is that you reason about state at a level that makes sense for all the interactions in the application. It is perfectly reasonable to hold both counters' state in the parent. I think the typical implementation would have a single update function in the parent, pass it, current counter value, and the counter IDs down as props, and have an update function in the child designed to invoke the parent's update function with the relevant counter ID.
Update: Your gist implementing this pattern is close to what I was talking about, keeping state in the parent and creating an onClick handler in the child that in turn calls its parent's update function, passing in parameters that let the parent know which counter to update. You may find it more useful to just pass the "refz" prop as that parameter rather than passing in the entire child React component, but as a proof of concept, you have the idea.

Categories

Resources