Im new in ReactJS...
I have a project with the following class components structure:
index.js
--app
--chat
--header
--left
--right
In the chat.js component, I make a google search with the api to retrieve images based on specific keyword... My intuitive solution was:
this.client.search("cars")
.then(images => {
for(let el of images) {
ReactDOM.render(<img src="{{el.url}}" syle="{{width: '100%'}}" />, document.querySelector('#gimages'));
}
});
It is correct? Or I may to use Components with stored states with flux (redux)?
Perhaps a simpler more conventional use of react would achieve what your require?
You could follow a pattern similar to that shown below to achieve what you require in a more "react-like" way:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = { images : [] } // Set the inital state and state
// model of YourComponent
}
componentDidMount() {
// Assume "client" has been setup already, in your component
this.client.search("cars")
.then(images => {
// When a search query returns images, store those in the
// YourComponent state. This will trigger react to re-render
// the component
this.setState({ images : images })
});
}
render() {
const { images } = this.state
// Render images out based on current state (ie either empty list,
// no images, or populated list to show images)
return (<div>
{
images.map(image => {
return <img src={image.url} style="width:100%" />
})
}
</div>)
}
}
Note that this is not a complete code sample, and will require you to "fill in the gaps" with what ever else you have in your current Chat component (ie setting up this.client)
This is not the way you should go, you don't need to use ReactDOM.render for each item. Actually, you don't need to use ReactDOM.render at all. In your component you can use a life-cycle method to fetch your data, then set it to your local state. After getting data you can pass this to an individual component or directly render in your render method.
class Chat extends React.Component {
state = {
images: [],
}
componentDidMount() {
this.client.search( "cars" )
.then( images => this.setState( { images } ) );
}
renderImages = () =>
this.state.images.map( image => <Image key={image.id} image={image} /> );
render() {
return (
<div>{this.renderImages()}</div>
);
}
}
const Image = props => (
<div>
<img src={props.image.url} syle="{{width: '100%'}}" />
</div>
);
At this point, you don't need Redux or anything else. But, if you need to open your state a lot of components, you can consider it. Also, get being accustomed to using methods like map, filter instead of for loops.
Related
I'm trying to update a prop value of a component in a list of components. Following is an example of it.
I'm developing an app using ReactNative
...
constructor(props) {
state = {
components: [*list of components*],
}
componentDidMount() {
fetchingAPI().then(response => {
const components = [];
for (const data of response.data) {
components.push(<MyComponent numOfLike={data.numOfLike} />);
}
this.setState({components});
});
}
render() {
return (
...
{this.state.components}
...
);
}
When I want to update a component, I update the whole state named components like :
updateAComponent(index, newNumOfLike) {
const components = this.state.components;
components[index] = <MyComponent numOfLike={newNumOfLike} />
this.setState({components});
}
But, this method change the component, not update. right? I means the components state is updated but MyComponent in components[index] is changed.
So, if I want to update the MyComponent in components[index] using the way of update the props numOfLike directly, how can I do it?
addition :
What I did not mention is that the MyComponent has a Image tag in it. So if I use FlatList or array.prototype.map there are several issues.
If I update the state, the whole list will be re-rendered. So if there are many list item, the speed of updating is very slow.
Since there are Image tag in the list, if I update a list item, the whole Image tags blink since the list items are re-rendered.
In this situation
Is there way to re-render(update) only a component which I want to update? (target updating)
If there in no way to target updating, just let the whole list items(components) re-rendered when just a component is updated?
You can use setNativeProps, described in the direct manipulation documentation
components[index].setNativeProps(propsObj)
You can modify your componentDidMount function like this (so that there are no race around or async conditions in the code) -:
componentDidMount() {
fetchingAPI().then(response => {
this.setState({
components: this.state.components.concat(
response.data.map(i => <MyComponent numOfLike={i.numOfLike} />)
)});
});
}
Can you try with the FlatList?
eg:
...
constructor(props) {
state = {
componentsData: [],
}
componentDidMount() {
fetchingAPI().then(response => {
this.setState({componentsData: response.data});
});
}
_renderItems = ({ item, index }) => {
return(
<MyComponent numOfLike={item. numOfLike} />
)
}
render() {
return (
...
<FlatList
data={this.state.componentsData}
renderItem={this._renderItems}
keyExtractor={(item, index) => index.toString()}
extraData={this.state}
/>
...
);
}
Then when you want to update the list,
updateAComponent(index, newNumOfLike) {
const data = this.state.componentsData;
data[index].numOfLike = newNumOfLike
this.setState({componentsData: data});
}
My app has multiple Popover components, I know how to handle the state of one Popover component, using something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = { pop_open: false };
}
handleProfileDropDown(e) {
e.preventDefault();
this.setState({
pop_open: !this.state.pop_open,
anchorEl: e.currentTarget,
});
}
handleRequestClose() {
this.setState({
pop_open: false,
});
};
render() {
return (
<div>
<button type="submit" onClick={this.handleProfileDropDown.bind(this)} >My Customized PopOver</button>
<Popover
open={this.state.pop_open}
anchorEl={this.state.anchorEl}
onRequestClose={this.handleRequestClose.bind(this)}
>
{"content"}
</Popover>
</div>
);
}
}
But for more than one Popover, I do not know how to do that, should I create a state for each Popover? Sorry for the question but I am new to the frontend world.
note: kindly do not use hooks in your answer.
An internal state is a good option when only the Component is going to modify it. It keeps the logic simple and inside the same block of code. On the other hand managing the state from outside of the Component lets other components read its values and modify them. This is a common approach when using Redux or Context, where there is a global app state. This state is meant for properties that several Components need to read/write to.
Which to use when is a design decision and depends on each situation. In my opinion each Component should handle its own state when possible. For example, when values are only going to be modified by it, or a children Component. Having an external state makes sense when multiple Components are going to read or modify it, or when the state values need to be passed several levels deep in the hierarchy.
In the example you propose I can see that the Popover is working with an internal state. This can work and you can use the Component several times and it will carry all the logic inside. If you rename the Components you can see more easily what I mean. I dont know exactly how the Component with the button works but this is to make the explanation clear:
class Popover extends Component {
constructor(props) {
super(props);
this.state = { is_open: false };
}
open = () => {
this.setState({
is_open: true
});
}
close = () => {
this.setState({
is_open: false
});
}
toggle = () => {
this.setState(prevState => ({
is_open: !prevState.is_open
}));
}
render() {
return (
<div>
<button onClick={this.toggle}>
Open
</button>
{this.state.is_open && <PopoverContent />}
</div>
);
}
}
If you need further explanation or something is not clear, let me know.
I'm pretty new to react native and async programming, and trying to understand how to "sync" redux state values and local state values.
For example, I have a text field "aboutMe" stored server side, and using mapStateToProps to place it into props:
const mapStateToProps = (state) => {
return { aboutMe: state.aboutMe };
}
In render, I have a TextInput I'm using so that the user can edit this field, and I would like to default to what is saved on the server side:
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
Basically, somewhere I need to call
this.setState({ aboutMe: this.props.aboutMe });
Where is the right place to this? I was trying to use componentWillReceiveProps, but that lifecycle method is not called on constructor, so I would need to setState twice (in constructor and in componentWillReceiveProps).
Is there another way to do this? I feel like this is a pretty generic problem that a lot of react native developers have solved but I couldn't find a generally accepted way online.
Thanks!
Edit:
I have alot of TextInputs, so I have a separate button to call the action to save the variables:
<Button onPress={()=>{
this.props.saveUserInput(this.state.aboutMe,
this.state.name, this.state.address, ....}}>
<Text> Save changes </Text>
</Button>
From the comments, I understand that it's possible to call the save action onChangeText... but is that too much traffic back and forth? Would it be better to save all of the variables locally to state and then call a save for everything at once? Also, what if the user would like to "cancel" instead of save? The changes would have been already saved and we will not be able to discard changes?
1) If your component is a controlled component (you need state in it) and the request is asynchronous indeed you have to set the state in the componentWillReceiveProps like this:
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: ""
}
}
componentWillReceiveProps(nextProps) {
this.setState({
aboutMe: nextProps.aboutMe,
});
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
Keep in mind the key here is that the state must remain the single source of truth from now on.
2) The other option would be, you can wait until the request is finished in the parent component and then set the aboutMe in your constructor, this way you can avoid componentWillReceiveProps. For example:
class ParentComp extends Component {
render() {
return (
<div>
{this.props.aboutMe && <ExampleComp/>}
</div>
);
}
}
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: props.aboutMe
}
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
The downside of this is that the text input won't be shown until the request is finished.
Since you have edited your question, it is more clear what you want to achieve, so I want to address that.
You could keep the state of your controlled input elements in the component, then use the redux store to store persistent data and to populate the default values.
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
aboutMe: props.aboutMe,
... // other data
}
}
handleSubmit = (event) => {
event.preventDefault() // To prevent redirect
// Dispatch the save user input action
this.props.dispatch(saveUserInput(this.state))
}
render() {
return (
<form onSubmit={this.handleSubmit} />
<TextInput onTextChange={text => this.setState({...this.state, aboutMe: text}) />
... // input fields for other data
// Clicking this fill trigger the submit event for the form
<button type="submit">Save</button>
</form>
)
}
}
I'm doing a simple project that has something like 3 forms and right now I start the component with empty Inputs and then request data from API to pre-populate the form using the componentWillMount() hook.
It works for me now but if someday my app need more and more data it would be annoying to do this everytime for any new form and I would like to know if there is any lib or pattern to help pre-populating forms without using any state container (Redux, mobx, and I really don't know if they are needed in this case).
It is better to do your data fetching in componentDidMount than in componentWillMount:
If you need to load data from a remote endpoint, this is a good place
to instantiate the network request.
If you want to reuse some data fetching logic without any external state you could use Component with render props or Higher Order Components.
For example:
function withData(fetchData) {
return BaseComponent => {
class WithData extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount(){
fetchData().then(response => {
this.setState({ data: response })
})
}
render(){
return <BaseComponent {...this.props} data={this.state.data} />
}
}
return WithData;
}
}
And later you can reuse this logic:
const DataList = ({ data }) => (
<ul>
{
data.map(item =>
<li>{item.name}</li>
)
}
</ul>
)
// passing Promises as a `data` producers
const UserDataList = withData(fetchUsers)(DataList);
const GroupDataList = withData(fetchGroups)(DataList);
const CatsDataList = withData(() => fetchAnimals('cats'))(DataList);
const ListOfEverything = () => (
<Container>
<UserDataList />
<GroupDataList />
<CatsDataList />
</Container>
)
Is there a way to log 1 object or 2 or as much as i want to be logged in console?
Im using simple data from jsonplaceholder.typicode.com (quite usefull for learning purpose) which every of object has unique id.
For example:
I fetched data and rendered 200 posts on website, ok... but if i have a data which contains 100 logos or banners or layouts for website i want to render a specific logo or banner with unique id 30.
So, how can I render only 1st, 2nd, 3rd or x post/posts from 200?
This is what i have now:
App.js
import React, { Component } from 'react';
import './css/App.css';
import './css/bootstrap.css';
import $ from 'jquery';
import Todos from './Todos';
class App extends Component {
constructor(props) {
super(props);
this.state={
todos:[],
};
}
getTodos() {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/todos',
dataType: 'json',
cache: false,
success: function (data) {
this.setState({todos: data}, function () {
console.log(this.state);
});
}.bind(this),
error: function (xhr, status, err) {
console.log(err);
}
})
}
componentWillMount(){
this.getTodos();
}
componentDidMount() {
this.getTodos();
}
render() {
return (
<div>
<Todos todos={this.state.todos}/>
</div>
);
}
}
export default App;
Todos.js
import React, {Component} from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
let todoItems;
if(this.props.todos) {
todoItems = this.props.todos.map(todo => {
return (
<TodoItem key={todo.title} todo = {todo} />
);
});
}
return (
<div>
<h2>Todo list from api</h2>
{todoItems}
</div>
);
}
}
Todos.propTypes = {
todos: React.PropTypes.array
};
export default Todos;
TodoItem.js
import React, {Component} from 'react';
class TodoItem extends Component {
render() {
return (
<li>
<strong>{this.props.todo.title}</strong>
</li>
);
}
}
TodoItem.propTypes = {
todo: React.PropTypes.object
};
export default TodoItem;
#edit
Is there possible to filter mapped json data and find object depending on id and then render this object?
Slicing multiple items
You can use another state property, like filterCount, which can be either set by you manually, or you can trigger setState events from buttons e.g.
constructor(props) {
super(props);
this.setFilter = this.setFilter.bind(this);
this.state={
todos: [],
filterCount: 20 // Default value
};
}
setFilter(count) {
this.setState({ filterCount: count });
}
render() {
const { todos, filterCount } = this.state;
return(
<div>
...
<button onClick={this.setFilter(10)} />
<button onClick={this.setFilter(20)} />
<button onClick={this.setFilter(50)} />
</div>
)
}
This will ensure that your component is rerendered each time when you change the count.
Now the second part, filtering the first x items.
The main way is to use Array.prototype.slice
Slice example:
render() {
const { todos, filterCount } = this.state;
const filteredToDos = todos.slice(0,filterCount);
return(
<div>
...
{ filteredToDos.map(todo => {
return (
<TodoItem key={todo.title} todo={todo} />
);
}) }
</div>
)
}
Make sure you don't accidentally use splice instead of slice, because splice doesn't do the copy to a new array, but modifies the original one.
This example can be easily modified to support paging aswell. You could add a currentPage parameter to state, and just modify the slice line to slice the array based on which page you are.
Also, think about hard whether you need to use jQuery in your app. If you are using it only for convenience, then you are making a big mistake, as it is a hefty library that increases your bundle size considerably. Try to learn doing things the react way :)
Your ajax request can be done by using fetch api.
return fetch("https://jsonplaceholder.typicode.com/todos")
.then(response => response.json())
.catch((error) => {
console.error(error);
});
If you don't need out of the box support for the older browsers, fetch api will be fine for last 2-3 major versions of modern browsers, including mobile ones. It can be polyfilled for older ones such as IE8 aswell.
Also, there are libraries like axios that are actually much smaller then jQuery if you need more options and support for all request types and headers.
Also, the fetch action itself, could be decoupled from the component to a separate actions.js file so it can be imported / used from multiple components in the future, or refactored more easily to support working with something like Redux if your app grows.
Getting a single item
Fetching single item from API directly - suggested
If we are talking about performance, then the best way is to get a single item directly from API. Considering that this is a REST based api then the way would be:
https://jsonplaceholder.typicode.com/todos/{id}
Rewriten for a fetch example with a template literal:
return fetch(`https://jsonplaceholder.typicode.com/todos/${itemId}`)
.then(response => response.json())
.catch((error) => {
console.error(error);
});
This should also return a single object, not an array, so you should be able to pass it as a prop and use it immediately. IMHO, this is the best solution to the problem.
Fetching all items from API, then filtering with filter
Second option is to get all layouts, then filter the result which you can see in the example of: #mayank-shukla
Bottleneck of this method is that Array.prototype.filter can return multiple results.
Fetching all items from API, then filtering with find
Third option is to get all layouts, then filter the result with Array.prototype.find
Example is the same as filter, just change the keyword to find.
Find will return the first result in case of multiple ones.
Fetching all items from API, then filtering by index
Fourth option is getting all layouts, then filtering by index, e.g.
todos[itemId-1]
This will also work, but, you need to be 100% certain that backend will order the results in the array by their id. Also, index starts from 0, and your ids from 1, so this can be a source of major confusion.
Instead of passing all the data to child component filter out the data on the basis of condition and pass that filtered data to child component.
Like this:
render() {
//filter data of these ids
let ids = [1,2,3,4];
//filtered data
let data = this.state.todos.filter(el => ids.indexOf(el.id) != -1);
return (
<div>
<Todos todos={data}/>
</div>
);
}
If you want to pass first x data, the use #array.slice and pass the result, like this:
render() {
//filtered data
let data = this.state.todos.slice(0, 20);
return (
<div>
<Todos todos={data}/>
</div>
);
}
Instead of putting the filter part inside render you can do that after getting the response.